// 对上传的文件进行标记
let uid = 1;
// 定义上传成功和失败的方法
const parseSuccess = xhr => {
  let response = xhr.responseText;
  if (response) {
    try {
      return JSON.parse(response);
    } catch (error) {
      return response;
    }
  }
}
const parseError = xhr => {
  let msg = '';
  let { responseText, responseType, status, statusText } = xhr;
  if (!responseText && responseType === 'text') {
    try {
      msg = JSON.parse(responseText)
    } catch (error) {
      msg = responseText
    }
  } else {
    msg = `${status} ${statusText}`
  }

  const err = new Error(msg);
  err.status = status;
  return err;
}
//内部实现Uploader类
class Uploader {
  constructor(option = {}) {
    const defaultOption = {
      url: '', // 上传地址
      wrapper: document.body, // 父级元素
      multiple: false, // 多选
      limit: -1, // 文件个数
      autoUpload: true, // 自动上传
      accept: '*', // 接收类型
      header: {}, // xhr头部
      data: {}, // xhr 传参数据
      withCredentials: false // 表示跨域请求时是否带上凭证cookie
    }
    this.setting = Object.assign(defaultOption, option)
    this._init()
  }
  // 根据配置初始化
  _init () {
    this.uploadFiles = [];
    this.input = this._initInputElement(this.setting);
    this.changeHandler = e => { // input的onchange事件处理函数
      const files = e.target.files;
      const ret = this._callHook('choose', files); // 对文件进行检查
      if (ret !== false) {
        this.loadFiles(ret || e.target.files)
      }
    };
    this.input.addEventListener('change', this.changeHandler);
    this.setting.wrapper.appendChild(this.input);
  }
  // 创建input标签并设置属性
  _initInputElement (setting) {
    const el = document.createElement('input');
    Object.entries({ // 返回对象的可枚举属性的键值对数组
      type: 'file',
      accept: setting.accept,
      multiple: setting.multiple,
      hidden: true
    }).forEach(([key, value]) => {
      el[key] = value;
    })
    return el;
  }
  // 状态事件绑定与响应
  on (event, Fn) { // 绑定事件
    if (event && typeof Fn === 'function') {
      this['on' + event] = Fn;
    }
    return this;
  }
  _callHook (event, ...args) { // 事件响应
    if (event && this['on' + event]) {
      return this['on' + event].apply(this, args);
    }
    return;
  }
  // 检查文件
  checkFile (file) {
    return new Promise((resolve, reject) => {
      if (file.size < 1024 * 1024) {
        resolve(file);
        return;
      }
      let image = new Image();
      let reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function () {
        image.src = reader.result;
      }
      image.onload = function () {
        let canvas = document.createElement('canvas');
        let context = canvas.getContext('2d');
        canvas.width = Math.floor(image.width * 0.7);
        canvas.height = Math.floor(image.height * 0.7)
        context.drawImage(image, 0, 0, canvas.width, canvas.height);
        canvas.toBlob(function (blob) {
          if (blob.size < 1024 * 1024) {
            resolve(blob);
          } else {
            this.checkFile(blob)
          }
        }, 'image/jpeg')
      }
    })
  }
  // 装载文件列表
  async loadFiles (files) {
    if (!files) { // 没有文件返回
      return false;
    }
    // 判断上传文件的类型
    const type = Object.prototype.toString.call(files)
    if (type === '[object FileList]') {
      files = [].slice.call(files);
    } else if (type === '[object Object]' || type === '[object File]') {
      files = [files];
    }

    // 对图片进行压缩
    let newFiles = [];
    for (let item of files) {
      let el = await this.checkFile(item)
      newFiles.push(el);
    }

    // 判断数量是否超过限制
    if (this.limit !== -1 && files.length && files.length + this.uploadFiles.length > this.limit) {
      this._callHook('exceed', files);
      return false;
    }

    // 构建约定的数据格式
    this.uploadFiles = this.uploadFiles.concat(newFiles.map(file => {
      if (file.uid && file.rawFile) {
        return file
      } else {
        return {
          uid: uid++,
          rawFile: file,
          fileName: file.name || `${Date.now() % 1000}.jpg`,
          size: file.size,
          status: 'ready'
        }
      }
    }))

    this._callHook('change', this.uploadFiles)
    this.setting.autoUpload && this.upload()
    return true;
  }
  // 上传文件列表
  upload (file) {
    if (!this.uploadFiles.length && !file) {
      return;
    }
    if (file) {
      const target = this.uploadFiles.find(item => item.uid === file.uid || item.uid === file)
      target && target.status !== 'success' && this._post(target)
    } else {
      this.uploadFiles.forEach(file => {
        file.status === 'ready' && this._post(file)
      })
    }
  }
  // 交互方法
  chooseFile () {
    // 每次都需要清空value，否则同一文件不触发change
    this.input.value = '';
    this.input.click();
  }
  removeFile (file) {
    const id = file.id || file;
    const index = this.uploadFiles.findIndex(item => item.id === id);
    if (index > -1) {
      this.uploadFiles.splice(index, 1);
      this._callHook('change', this.uploadFiles);
    }
  }
  clear () {
    this.uploadFiles = [];
    this._callHook('change', this.uploadFiles);
  }
  destroy () {
    this.input.removeEventListener('change', this.changeHandler);
    this.setting.wrapper.removeChild(this.input);
  }
  // 发起请求
  _post (file) {
    if (!file.rawFile) {
      return;
    }
    const { header, data, withCredentials } = this.setting;
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file.rawFile, file.fileName);

    Object.keys(data).forEach(key => {
      formData.append(key, data[key]);
    })
    Object.keys(header).forEach(key => {
      xhr.setRequestHeader(key, header[key]);
    })

    file.status = 'uploading';
    xhr.withCredentials = !!withCredentials;

    xhr.onload = () => {
      // 处理响应
      if (xhr.status < 200 || xhr.status >= 300) {
        file.status = 'error';
        this._callHook('error', parseError(xhr), file, this.uploadFiles)
      } else {
        file.status = 'success';
        this._callHook('success', parseSuccess(xhr), file, this.uploadFiles)
      }
    }
    xhr.onerror = e => {
      // 处理失败
      file.status = 'error';
      this._callHook('error', parseError(xhr), file, this.uploadFiles);
    }
    xhr.upload.onprogress = e => {
      // 处理上传进度
      const { total, loaded } = e;
      e.percent = total > 0 ? loaded / total * 100 : 0;
      this._callHook('progress', e, file, this.uploadFiles);
    }
    xhr.open('post', this.setting.url, true);
    xhr.send(formData)
  }
}

export default Uploader;